package com.companyname.demo.cod.util;

import com.alibaba.fastjson.JSONObject;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.Verification;
import com.companyname.demo.cod.domain.AjaxResult;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.impl.TextCodec;
import io.jsonwebtoken.impl.crypto.DefaultJwtSigner;
import io.jsonwebtoken.impl.crypto.JwtSigner;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.Base64Utils;

import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.util.*;

/**
 * @Description: JSON WEB TOKEN工具  JWT官网：https://jwt.io/introduction/
 * @Author: xglsr
 * @Date: 2021/6/3 10:12
 */
@Slf4j
public class JwtUtil {
    private static String JWT_ISSUER = "yourCompanyName";//JWT签发者

    /**
     * 生成JWT
     *
     * @param customParams payload中的自定义参数,比如不敏感的用户信息
     * @param jwtSecret    秘钥，使用用户密码加密规则的salt
     */
    public static String createJwt(Map<String, Object> customParams, String jwtSecret) {
        return Jwts.builder()
                .setHeader(createJwtHeader())
                .addClaims(createJwtPayload(customParams))
                .signWith(SignatureAlgorithm.HS256, Base64Utils.encodeToString(jwtSecret.getBytes()))
                .compact();
    }

    private static Map<String, Object> createJwtHeader() {
        Map<String, Object> map = new HashMap<>();
        map.put(JwsHeader.TYPE, JwsHeader.JWT_TYPE);
        map.put(JwsHeader.ALGORITHM, SignatureAlgorithm.HS256.getValue());//算法名称
        return map;
    }

    /**
     * 创建payload(载荷)中的声明部分
     *
     * @param customParams 自定义参数,比如不敏感的用户信息
     */
    private static Map<String, Object> createJwtPayload(Map<String, Object> customParams) {
        Map<String, Object> map = new HashMap<>();
        //标准中注册的声明部分（建议但不强制参数）
        map.put(Claims.ISSUER, JWT_ISSUER);//jwt签发者
//        map.put(Claims.SUBJECT, null);//jwt所面向的用户
//        map.put(Claims.AUDIENCE,null);//接收jwt的一方
//        map.put(Claims.EXPIRATION, null);//jwt过期时间，交由Redis控制
//        map.put(Claims.NOT_BEFORE, null);//定义在某个时间点之前，该jwt都是不可用的
        map.put(Claims.ISSUED_AT, new Date().getTime() / 1000);//jwt签发时间,单位s
//        map.put(Claims.ID, null);//jwt的唯一身份表示，主要用来作为一次性token，从而回避重放
        if (Objects.nonNull(customParams)) {
            map.putAll(customParams);
        }
        return map;
    }

    /**
     * 根据Base64编码的header payload secret创建Signature
     */
    private static String createSignature(String base64UrlEncodedHeader, String base64UrlEncodedPayload, String base64UrlEncodedSecret) {
        byte[] keyBytes = TextCodec.BASE64.decode(base64UrlEncodedSecret);
        Key key = new SecretKeySpec(keyBytes, SignatureAlgorithm.HS256.getJcaName());
        JwtSigner signer = new DefaultJwtSigner(SignatureAlgorithm.HS256, key);
        String jwt = base64UrlEncodedHeader + '.' + base64UrlEncodedPayload;
        String base64UrlSignature = signer.sign(jwt);
        return base64UrlSignature;
    }

    /**
     * 解析JWT
     *
     * @param jwtSecret 秘钥，使用用户密码加密规则的salt
     * @return 返回的是DecodedJWT对象，可通过getHeader()、getPayload()、getSignature()来获得对应部分。
     * 其中header、payload可以通过Base64解码，signature是由HMACSHA256算法创建的
     */
    public static DecodedJWT parseJWT(String token, String jwtSecret) throws Exception {
        DecodedJWT decodedJWT = null;
        if (StringUtils.isBlank(token) || StringUtils.isBlank(jwtSecret)) {
            return decodedJWT;
        }
        //要先对jwtSecret进行Base64编码
        Verification require = JWT.require(Algorithm.HMAC256(jwtSecret.getBytes()));
        decodedJWT = require.build().verify(token);
        return decodedJWT;
    }

    /**
     * 校验jwt的有效性
     */
    public static AjaxResult checkJwt(String userToken) throws Exception {
        //1.如果token为空
        if (StringUtils.isBlank(userToken)) {
            return AjaxResult.error401("token为空");
        }
        //2.解析jwt.payload
        String requestJwtHead = userToken.split("\\.")[0];
        String requestJwtPayload = userToken.split("\\.")[1];
        String requestJwtSignature = userToken.split("\\.")[2];

        JSONObject payloadJSON = (JSONObject) JSONObject.parse(Base64Utils.decode(requestJwtPayload.getBytes()));
        String userName = (String) payloadJSON.get("userName");
        long requestTimestamp = (long) payloadJSON.get("timestamp");

        //3.校验该用户的jwtsalt是否存在
        RedisTemplate redisTemplate = (RedisTemplate) SpringUtil.getBean("redisTemplate");
        String jwtsaltValue = (String) redisTemplate.opsForValue().get("jwtsalt:" + userName);
        if (StringUtils.isBlank(jwtsaltValue)) {//jwt超时失效，重新登录
            return AjaxResult.error603("jwtsalt不存在");
        }
        //4.校验timestamp是否一致
        long redisTimestamp = Long.parseLong(jwtsaltValue.split("\\.")[1]);
        if (requestTimestamp != redisTimestamp) {
            return AjaxResult.error602("异地登录被挤下线");
        }

        //5.根据该用户的salt，重新计算Signature并判断是否一致
        String userSalt = jwtsaltValue.split("\\.")[0];
        String createdSignatureByReq = createSignature(requestJwtHead, requestJwtPayload, Base64Utils.encodeToString(userSalt.getBytes()));
        if (!requestJwtSignature.equals(createdSignatureByReq)) {
            return AjaxResult.error601("Token.Signature校验不合法");
        }

        return AjaxResult.success("token校验通过");
    }


    public static void main(String[] args) throws Exception {
        String salt = "privatesalt";
        Map<String, Object> userInfo = new HashMap<>();
        userInfo.put("userName", "xgl");
        userInfo.put("userType", "Admin");
        userInfo.put("entName", "企业名称");
        String jwt = createJwt(userInfo, salt);
        System.out.println("created jwt = " + jwt);

        DecodedJWT decodedJWT = parseJWT(jwt, salt);
        System.out.println("decodedJWT.payload= " + decodedJWT.getPayload());

    }

}
